﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MongoDB.Bson;
using MongoDB.Shared;
using MongoDB.Driver;
using MongoDB.Driver.Communication;
using MongoDB.Driver.Support;
using MongoDB.Driver.Wrappers;
using MongoDB.Driver.Builders;
using System.Configuration;

namespace UsageTracker.MongoDb.Processor
{
    using System.Reflection;
    using System.Web.Script.Serialization;

    using log4net;

    using MongoDB.Bson.IO;
    using MongoDB.Bson.Serialization;
    using MongoDB.Bson.Serialization.Attributes;
    using MongoDB.Bson.Serialization.Serializers;

    public class DbProcessor
    {
        private readonly ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
        private MongoCollection<BsonDocument> collection = null;

        /// <summary>
        /// 
        /// </summary>
        public DbProcessor()
        {

        }

        /// <summary>
        /// Default Constructor
        /// </summary>
        /// <param name="collectionName">Name of the document collection to be manipulated</param>
        public DbProcessor(string collectionName)
        {
            this.collection = ConnectToCollection(collectionName);
        }


        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public List<string> GetAllCollections()
        {
            List<string> collections = new List<string>();
            var server = ConnectToServer();
            var dbName = ConfigurationManager.AppSettings.Get("MONGO_DATABASE_NAME");
            var db = server.GetDatabase(dbName);
            return db.GetCollectionNames().ToList();
        }

        /// <summary>
        /// Gets a handle to the specific collection.
        /// </summary>
        /// <param name="collectionName"></param>
        /// <returns></returns>
        private MongoCollection<BsonDocument> ConnectToCollection(string collectionName)
        {
            var server = ConnectToServer();
            var dbName = ConfigurationManager.AppSettings.Get("MONGO_DATABASE_NAME");
            var database = server.GetDatabase(dbName);
            MongoCollection<BsonDocument> mongocollection = null;
            try
            {
                mongocollection = database.GetCollection<BsonDocument>(collectionName);
                if (mongocollection == null)
                {
                    log.Error(
                        "The requested Mongo Collection was not found. Creating a new collection with name = "
                        + collectionName);
                }
            }
            catch (Exception exp)
            {
                log.Error("Error in getting Mongo Collection" + exp.StackTrace);
            }
            return mongocollection;
        }


        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        private static MongoServer ConnectToServer()
        {
            var connectionString = ConfigurationManager.AppSettings.Get("MONGO_CONNECTION");
            var client = new MongoClient(connectionString);
            var server = client.GetServer();
            return server;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="entity"></param>
        public void Insert(string entity)
        {
            lock (collection)
            {
                try
                {
                    BsonDocument bDoc = BsonSerializer.Deserialize<BsonDocument>(entity);
                    bDoc.Elements.ToList().ForEach(
                        r =>
                        {
                            //IMP - Transform the string based DateTime to a Bson string for MongoDb for proper date handling
                            if (r.Name == "LogDateUtc")
                            {
                                r.Value = new BsonDateTime(DateTime.Parse(r.Value.ToString()));
                            }
                        });

                    log.Info("MongoQuery INSERT: " + bDoc.ToString());
                    WriteConcernResult result = this.collection.Insert(bDoc);
                }
                catch (Exception exp)
                {
                    Console.WriteLine(exp.ToString());
                }
            }
        }


        /// <summary>
        /// 
        /// </summary>
        /// <param name="productName"></param>
        /// <param name="host"></param>
        /// <returns></returns>
        public Dictionary<string, Dictionary<string, int>> GetProductModuleUsageDetails(string productName, DateTime startDate, DateTime endDate, string host = null)
        {
            //Hashtable to store the modulewise usage distribution for a specific instance of an application
            Dictionary<string, int> moduleDistribution = new Dictionary<string, int>();

            //Hashtable to store the location (m/c name) and modulewise usage distribution for a product
            Dictionary<string, Dictionary<string, int>> appDistribution = new Dictionary<string, Dictionary<string, int>>();

            BsonDocument doc = new BsonDocument();
            var qry = Query.And(Query.EQ("ProductName", productName), Query.GTE("LogDateUtc", startDate), Query.LTE("LogDateUtc", endDate));
            BsonDocument qryPredicates = qry.ToBsonDocument();

            //Add the AND condition in the query if a host name is specified
            if (!String.IsNullOrEmpty(host))
            {
                qryPredicates.Add(new BsonElement("MachineName", host));
            }

            doc.AddRange(qryPredicates);
            QueryDocument qryDoc = new QueryDocument(doc);

            log.Info("MongoQuery: " + qryDoc.ToString());
            MongoCursor<BsonDocument> result = this.collection.FindAs<BsonDocument>(qryDoc);

            //First get all working installations
            var lst = result.Where(r => r["ProductName"] == productName).GroupBy(a => a["MachineName"]).Select(r => r).ToList();
            foreach (var groupItem in lst)
            {
                var item = groupItem.FirstOrDefault();
                string machineName = item["MachineName"].ToString();

                var lstInstall = groupItem.Where(i => ((i["ProductName"] == productName) && (i["MachineName"] == machineName))).GroupBy(a => new { Module = a["Module"], ModVersion = a["ModVersion"] }).Select(r => r).ToList();
                //Iterate thru each module of a specific installation
                foreach (var install in lstInstall)
                {
                    var installItem = install.FirstOrDefault();
                    int memberCount = install.Count();
                    string modName = installItem["Module"].ToString();
                    modName = modName.Replace(".dll", "");
                    moduleDistribution[modName + " (" + installItem["ModVersion"].ToString() + ")"] = memberCount;
                }
                appDistribution[machineName] = moduleDistribution;
            }

            return appDistribution;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="productName"></param>
        /// <returns></returns>
        public Dictionary<string, string> GetProductGeoDistribution(string productName, DateTime startDate, DateTime endDate)
        {
            //Hashtable to store the modulewise usage distribution for a specific instance of an application
            Dictionary<string, string> geoDistribution = new Dictionary<string, string>();

            BsonDocument doc = new BsonDocument();
            var qry = Query.And(Query.EQ("ProductName", productName), Query.Exists("PublicIp"), Query.GTE("LogDateUtc", startDate), Query.LTE("LogDateUtc", endDate));
            BsonDocument qryPredicates = qry.ToBsonDocument();

            doc.AddRange(qryPredicates);
            QueryDocument qryDoc = new QueryDocument(doc);

            MongoCursor<BsonDocument> result = this.collection.FindAs<BsonDocument>(qryDoc);

            //First get all working installations
            var lst = result.Select(r => r).ToList();
            foreach (var appHosts in lst)
            {
                var machineName = appHosts["MachineName"].ToString();
                var machinePublicIp = appHosts["PublicIp"].ToString();
                geoDistribution[machineName] = machinePublicIp;
            }

            return geoDistribution;
        }
    }
}
